package operations;


import ga.GAInfo;

import java.io.IOException;
import java.util.Calendar;
import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Iterator;
import util.CanBeUsedAs;
import util.Randomness;
import mdr.MethodDependences;
import model.StatementKind;
import model.CUT;
import model.Filter;
import model.TestClusterBuilder;
import model.ConstructorDeclared;
import model.ConstantDeclared;
import model.PublicFieldDeclared;
import model.MethodDeclared;
import model.ArrayDeclared;

import util.GAConfig;
import ga.Individual;
public abstract class Operation   {
	// generation probabilities
    public static float PDiv = 0.25f;
    public static float GEN_NEW = 0.25f;
    public static float USE_Fields =0.15f;
    public static float USE_Arrays=0.15f;
    public static float SET_TO_NULL = 0.05f;
	public static float Select_Class_Cluster = 0.25f;//1.0f;

    public static List<String> alreadyCreatedClasses= new LinkedList<String>();
	@Override
	public abstract Operation clone();
	@Override
	public abstract String toString();
	public static  List<Operation> dependenceGenerateInitialConstructors(TestClusterBuilder TestCluster){
		List<Operation> initialConstructors= new LinkedList<Operation> ();
		Map<String, CUT>  cluster= TestCluster.getCluster();
		Map<String, String[]> refFactory=TestCluster.getRefFactory();
		for(String cName: refFactory.keySet()){
			String[] refrences = refFactory.get(cName);
			CUT cut=cluster.get(cName);
			Class<?> clazz = cut.getClazz();
			if(Filter.isPrimitiveOrStringType(clazz))continue;
			if(Filter.isObjectType(clazz))continue;
				//create arrays
			for(int k=0;k<cut.getArrays().length;k++){
				for (int i = 0; i < refrences.length; i++) {
					Operation creatArry=(Operation) CreateArray.generate(cut,TestCluster,cut.getArrays()[k]);
					if(creatArry!=null){
						StatementKind statKind=((CreateArray) creatArry).getAnArrayStatKind();
						String clzName=statKind.getOutputType().getName();
						String[] arrRefrences=refFactory.get(clzName);
						((CreateArray)creatArry).setRef(arrRefrences[i]);
						 initialConstructors.add(i,creatArry);
					}
				}
			}
			if(Filter.isAbstract(clazz))
				clazz=generateReference(clazz, TestCluster);
			for (int i = 0; i < refrences.length; i++) {
				if(alreadyCreatedClasses.contains(refrences[i])) continue;
					 initialConstructors.addAll(Operation.buildConstructor(cut, TestCluster,i));
			}
		}		
		alreadyCreatedClasses.clear();
		return initialConstructors;
	}
	public static  List<Operation> randomGenerateInitialConstructors(TestClusterBuilder TestCluster){
		List<Operation> initialConstructors= new LinkedList<Operation> ();
		Map<String, CUT>  cluster= TestCluster.getCluster();
		Map<String, String[]> refFactory=TestCluster.getRefFactory();
		for(String cName: refFactory.keySet()){
	//		System.out.println("Operation.randomGenerateInitialConstructors : "+ cName);
			String[] refrences = refFactory.get(cName);
			CUT cut=cluster.get(cName);
			Class<?> clazz = cut.getClazz();
			if(Filter.isPrimitiveOrStringType(clazz))continue;
			if(Filter.isObjectType(clazz))continue;
				//create arrays
			for(int k=0;k<cut.getArrays().length;k++){
				for (int i = 0; i < refrences.length; i++) {

					Operation creatArry=(Operation) CreateArray.generate(cut,TestCluster,cut.getArrays()[k]);
					if(creatArry!=null){
						StatementKind statKind=((CreateArray) creatArry).getAnArrayStatKind();
						String clzName=statKind.getOutputType().getName();
						String[] arrRefrences=refFactory.get(clzName);
						((CreateArray)creatArry).setRef(arrRefrences[i]);
						if(alreadyCreatedClasses.contains(arrRefrences[i])) continue;
						initialConstructors.add(i,creatArry);
						 alreadyCreatedClasses.add(arrRefrences[i]);
					}
				}
			}
			if(Filter.isAbstract(clazz))
				clazz=generateReference(clazz, TestCluster);
			for (int i = 0; i < refrences.length; i++) {
				if(alreadyCreatedClasses.contains(refrences[i])) continue;
					 initialConstructors.addAll(Operation.buildConstructor(cut, TestCluster,i));
			}
		}		
		alreadyCreatedClasses.clear();
		return initialConstructors;
	}
	private  static List<Operation> buildConstructor(CUT cut,TestClusterBuilder TestCluster,int ref_indx){

		Map<String, CUT>  cluster= TestCluster.getCluster();
		Map<String, String[]> refFactory=TestCluster.getRefFactory();
		List<Class<?>> replacementObjectParam= new LinkedList<Class<?>>();

		List<Operation> neededConstr= new LinkedList<Operation> ();
		   List <Object> values=new LinkedList<Object>();
		   Class<?> clazz = cut.getClazz();		
		   String[] refrences = refFactory.get(clazz.getName());

		if(cut.getConstructors().length==0) return neededConstr;
		StatementKind constructor = cut.getConstructors()[Randomness.nextRandomInt(cut.getConstructors().length)];
//		System.out.println("Operation Consider Class:  " +  constructor.toString());
		   List<Class<?>> formalParams = constructor.getInputTypes();
			Iterator<?> t = formalParams.iterator();
			while (t.hasNext()) {
				Class<?> paramTpe = (Class<?>) t.next();
					if (Filter.isPrimitiveOrStringType(paramTpe))
						values.add(ValuesGenerator.generatePrimitiveValue(paramTpe));
					else{
						if(Filter.isObjectType(paramTpe)||paramTpe.equals(java.lang.Class.class)){
							paramTpe=generateReference(paramTpe, TestCluster);
						}
						else{
							Class<?> tempParaTyp=paramTpe;
							if((paramTpe.isInterface()||Filter.isAbstract(paramTpe))&&!paramTpe.getName().startsWith("java")&&!paramTpe.isArray())
								paramTpe=generateReference(paramTpe, TestCluster);
							if(paramTpe==null){//class to interface of abstract there is no assignable to it
								replacementObjectParam.add(tempParaTyp);
								values.add(null);
								continue;
							}
	
						}
						if (Filter.isPrimitiveOrStringType(paramTpe))
							values.add(ValuesGenerator.generatePrimitiveValue(paramTpe));
						else{
							String[] refrences_ = refFactory.get(paramTpe.getName());	
							neededConstr.addAll(Operation.buildConstructor(cluster.get(paramTpe.getName()),TestCluster,ref_indx));
							if(!alreadyCreatedClasses.contains(refrences_[ref_indx])){
								 alreadyCreatedClasses.add(refrences_[ref_indx]);

						   }
//						   if(ValuesGenerator.getBoolean(SET_TO_NULL))
							   values.add(refrences_[ref_indx]);
						}
					}

					replacementObjectParam.add(paramTpe);
			}

			Operation op =(Operation) new  CreateObject(refrences[ref_indx],constructor,values);
			if(op instanceof CreateObject)
				op.addInfo(new OperationResult());
			neededConstr.add(op);
		return neededConstr;
		
	}
	public static List<Operation> randomlyGenerateOperation(TestClusterBuilder TestCluster, int testLength){
		List<Operation> ops = new LinkedList<Operation>();
		while (ops.size()<testLength) {
			//List<Operation> listSequences= new LinkedList<Operation>();
			Operation op=null;
			CUT cut=null;
			try {
				cut=TestCluster.getCut();
				//cut=TestCluster.getClassCluster();
	//			System.err.println("Operation.randomlyGenerat: "+ cut.getClazz().getName() );
				if(ValuesGenerator.getBoolean(Select_Class_Cluster))  cut=TestCluster.getClassCluster();
					if (ValuesGenerator.getBoolean(GEN_NEW)) {
						if(ValuesGenerator.getBoolean(USE_Arrays))
							op=(Operation) CreateArray.generate(cut,TestCluster,null);
						else
							if (ValuesGenerator.getBoolean(USE_Fields)){
								if(ValuesGenerator.getBoolean(0.5f))
										op=(Operation) AssignConstant.generate(cut, TestCluster);
								else
									op=(Operation) AssignPublicField.generate(cut, TestCluster);									
							}
							else 							
								op=(Operation)  CreateObject.generate(cut, TestCluster,null);
					} 
					else{					
						if(cut.getMethods().length!=0)
							op=(Operation) InvokeMethod.randomlyGenerateMethod(cut, TestCluster);
					}
					if (op!=null){
						if(op instanceof CreateObject||op instanceof InvokeMethod )
							op.addInfo(new OperationResult());
						ops.add(op);
					}
//				}//while(ValuesGenerator.getBoolean(1-PDiv))
			} catch (Throwable e) {
				System.err.println("Operation.randomlyGenerat: "+ cut.getClazz().getName() + " error "+e.getMessage());
			}
		}
		return ops;
	}
	public static List<Operation> recGenerateOperation(TestClusterBuilder TestCluster, int testLength,GAInfo gaInfo){
		List<Operation> ops = new LinkedList<Operation>();
		MethodDependences recommender=gaInfo.getMethodRelations();
		while (ops.size()<testLength) {
			//List<Operation> listSequences= new LinkedList<Operation>();
			Operation op=null;
			CUT cut=null;
			try {
				cut=TestCluster.getCut();
				Class<?> clz=cut.getClazz();
				//cut=TestCluster.getClassCluster();
	//			System.err.println("Operation.recGenerateOperation: "+ cut.getClazz().getName() );
				if(ValuesGenerator.getBoolean(Select_Class_Cluster))  cut=TestCluster.getClassCluster();
					if (ValuesGenerator.getBoolean(GEN_NEW)) {
						if(ValuesGenerator.getBoolean(USE_Arrays))
							op=(Operation) CreateArray.generate(cut,TestCluster,null);
						else
							if (ValuesGenerator.getBoolean(USE_Fields)){
								if(ValuesGenerator.getBoolean(0.5f))
										op=(Operation) AssignConstant.generate(cut, TestCluster);
								else
									op=(Operation) AssignPublicField.generate(cut, TestCluster);									
							}
							else 							
								op=(Operation)  CreateObject.generate(cut, TestCluster,null);
					} 
					else{					
						if(cut.getMethods().length!=0){

							StatementKind methodToTest = cut.getMethods()[Randomness.nextRandomInt(cut.getMethods().length)];
			//				System.out.println("Operation.recGenerateOperation: "+ methodToTest.toString() );
						
							 Map<Class<?>, Map<StatementKind, List<StatementKind>>> dependences=recommender.getRelations().getStatementDependences();
							 Map<StatementKind, List<StatementKind>> classDependeces=dependences.get(clz);
							 List<StatementKind> methodDependeces =classDependeces.get(methodToTest);
							 if(methodDependeces!=null){
								 int numberOmittedMethods=ValuesGenerator.getRandomIntBetween(0, methodDependeces.size()-1);
				//				System.out.println("getConfiguration number Omitted Methods:"+ numberOmittedMethods);
								List<StatementKind> recStatements = new LinkedList<StatementKind>();
								recStatements.addAll(methodDependeces);

								for (int i = 1; i <=numberOmittedMethods; i++) {
									int randMethod = Randomness.nextRandomInt(recStatements
											.size());
									StatementKind st = recStatements.get(randMethod);

									recStatements.removeAll(Collections.singleton(st));
								}
								
								if((testLength-ops.size()-1)!=0){
									int M=Randomness.nextRandomInt(10);//Randomness.nextRandomInt(testLength-ops.size()-1);
									ops.addAll(Operation.mdrGenerateOperation(TestCluster, null, M, recStatements));
								}
							 }
								//add methodTotest to the end of the generated sequences
								op=(Operation) InvokeMethod.recommendedgenerate(clz,null,methodToTest, TestCluster);

						}
					}
					if (op!=null){
						if(op instanceof CreateObject||op instanceof InvokeMethod )
							op.addInfo(new OperationResult());
						ops.add(op);
					}
//				}//while(ValuesGenerator.getBoolean(1-PDiv))
			} catch (Throwable e) {
				System.err.println("Operation.recGenerateOperation: "+ cut.getClazz().getName() + " error "+e.getMessage());
			}
		}
		return ops;
	}

	public static List<Operation> mdrGenerateOperation(TestClusterBuilder TestCluster, String ref,int testLength,  List<StatementKind> recStatements){
		List<Operation> ops = new LinkedList<Operation>();
		
		while (ops.size()<testLength) {
			try {
			//	List<Operation> op = new LinkedList<Operation>();
				Operation op=null;
				StatementKind sk=null;
				if(recStatements.size()!=0)
					sk=recStatements.get(Randomness.nextRandomInt(recStatements.size()));
				else
					return null;
				//if(sk.toString().contains("java.util.Vector")) continue;
				if(sk!=null){
					if(sk instanceof ConstructorDeclared && ValuesGenerator.getBoolean(GEN_NEW)){
							Class<?>clz=((ConstructorDeclared) sk).getConstructor().getDeclaringClass();
							op=(Operation) CreateObject.recommendedgenerate(clz,sk, TestCluster);
					}else if(sk instanceof ConstantDeclared && ValuesGenerator.getBoolean(USE_Fields)){
							Class<?>clz=((ConstantDeclared) sk).getConstant().getDeclaringClass();
							op=(Operation)AssignConstant.recommendedgenerate(clz,sk, TestCluster);
					}else if(sk instanceof PublicFieldDeclared && ValuesGenerator.getBoolean(USE_Fields)){
						Class<?>clz=((PublicFieldDeclared) sk).getField().getDeclaringClass();
						op=(Operation)AssignPublicField.recommendedgenerate(clz,sk, TestCluster);
					}else if(sk instanceof MethodDeclared){
							Class<?>clz=((MethodDeclared) sk).getMethod().getDeclaringClass();
							op=(Operation) InvokeMethod.recommendedgenerate(clz,ref,sk, TestCluster);
					}else if(sk instanceof ArrayDeclared && ValuesGenerator.getBoolean(USE_Arrays)){
						Class<?>clz=((ArrayDeclared) sk).getArray();
						op=(Operation) CreateArray.recommendedGenerate(clz,ref,sk, TestCluster);
					}
				}
//				else{
//					Class<?>clz=null;
//					CUT cut=TestCluster.getCluster().get(clz.getName());
//					op=(Operation)AssignConstant.generate(cut, TestCluster);
//				}
				if (op!=null){
					if(op instanceof CreateObject||op instanceof InvokeMethod )
						op.addInfo(new OperationResult());
					ops.add(op);
				}
			} catch (Throwable e) {
				System.err.println("Operation.mdrGenerateOperation: "+ e.getMessage());
			}
		}
		return ops;
	}

	public static Class<?> generateReference(Class<?> clz1,TestClusterBuilder TestCluster) {
		Map<String, CUT>  cluster = TestCluster.getCluster();
		List <Class<?>> equivalentClass=new LinkedList<Class<?>>();
		Class<?> clz2=null;;
		List<String> keys = new ArrayList<String>(cluster.keySet());
		for( ;equivalentClass.size()!=cluster.size();){
			int cutRand = Randomness.random.nextInt(cluster.size());

			String cName = (String) keys.get(cutRand);

			 clz2=((CUT)cluster.get(cName)).getClazz();
			 if((CanBeUsedAs.canBeUsedAs(clz2, clz1)))
				 	break;
			if(!equivalentClass.contains(clz2))
				equivalentClass.add(clz2);
			}//for
		 if(equivalentClass.size()==cluster.size()){
			 return null;
		 }
		 
		 return clz2;
	}
	public static Class<?> generateMDRReference(Class<?> clz1,List<StatementKind> BRRelatedStatementList,TestClusterBuilder TestCluster) {
		Map<String, CUT>  cluster = TestCluster.getCluster();
		List <StatementKind> equivalentClass=new LinkedList<StatementKind>();

		for( ;equivalentClass.size()!=BRRelatedStatementList.size();){
			int cutRand = Randomness.random.nextInt(BRRelatedStatementList.size());
			StatementKind statement= (StatementKind) BRRelatedStatementList.get(cutRand);
			if(!equivalentClass.contains(statement))
				equivalentClass.add(statement);

			if(!(statement instanceof ConstructorDeclared)) continue;
			Class<?> clz2=((ConstructorDeclared)statement).getConstructor().getDeclaringClass();
			if((CanBeUsedAs.canBeUsedAs(clz2, clz1)))
				 return clz2;;
		}//while(!CanBeUsedAs.canBeUsedAs(clz2, clz1));

		 return null;
	}

	
	public static String getReference(String cName,Map<String, String[]>  refFactory) {
		String refs[] = refFactory.get(cName);
		
		if(refs == null) return null;

		return refs[Randomness.nextRandomInt(refs.length)];
	}
	private Map<String, OperationInformation> infos;
	public OperationInformation getInfo(String key) {
		if(infos == null || key == null) return null;
		return infos.get(key);
	}
	public Iterator<OperationInformation> getInfos() {
		if(infos != null) 
			return infos.values().iterator();
		return null;
	}
	public void addInfo(Iterator<OperationInformation> iter) {
		while(iter.hasNext())
			addInfo(iter.next().clone());
	}

	public void addInfo(OperationInformation info) {
		if(infos == null) 
			infos = new HashMap<String, OperationInformation>();
		infos.put(info.getKey(),info);
	}

	public  static void main(String args[]) throws ClassNotFoundException,IOException {
		GAInfo gaInfo = new GAInfo();
		long startTime = Calendar.getInstance().getTimeInMillis();
		gaInfo.setStartTime(startTime);
	       Randomness.reset(20);
		
		 Set<Class<?>> allClasses = new LinkedHashSet<Class<?>>();
	      //get class from file
	      allClasses.addAll(TestClusterBuilder.readClassFromFile());

	      System.out.println("There are " + allClasses.size() + " classes as input");
	      
	      System.out.println(allClasses);
	//      TestClusterBuilder TestCluster = new TestClusterBuilder(allClasses);
	      List<TestClusterBuilder> TestClusterList= TestClusterBuilder.buildTestClusterList(allClasses);
	      for(TestClusterBuilder tCluster:TestClusterList){
		      Map<String,Class<?>> classesToTest = tCluster.getClassToTest();
		      CUT cut=tCluster.getCut();
		      System.out.println("Class " + classesToTest.keySet().toString() + " is being Tested");
		     // System.out.println(classesToTest.keySet().toString());
		      if(cut.getMethods().length==0){
			      System.out.println("Class " + classesToTest.keySet().toString() + " is being Tested");
			      System.out.println("\tThere are no methods to test!");
			      continue;
		      }
		      gaInfo.setTestCluster(tCluster);


		      List<Operation> ops=new LinkedList<Operation>();
		      List<Operation> initialConstructors=new LinkedList<Operation>();
		      if(GAConfig.randomInitialPopulation)
		    	   initialConstructors= Operation.randomGenerateInitialConstructors(tCluster);
		      else
		    	   initialConstructors= Operation.dependenceGenerateInitialConstructors(tCluster);
		    	  
		      System.out.println(initialConstructors.toString());
		      System.out.println("-----------------------------");
		     ops.addAll(Operation.randomlyGenerateOperation(tCluster,100));
		      Individual individual=new Individual(initialConstructors,ops);
		      System.out.println(individual.toString());
	      }
	      System.exit(0);
	}


}